#ifndef quantlib_tradingportfolio_hpp
#define quantlib_tradingportfolio_hpp


#include <string>
#include <map>
#include <vector>
#include "boost/shared_ptr.hpp"
#include <qt/tradeorder.hpp>
#include <qt/utilities/daterateholder.hpp>
#include <ql/errors.hpp>

namespace QuantLib {

	struct AssetData {
			AssetData(DateRateHolder pricesIn) 
				: prices(pricesIn) {}
			AssetData(DateRateHolder pricesIn, DateRateHolder dividendsIn)
				: prices(pricesIn),
				dividends(dividendsIn) {}

			DateRateHolder prices;
			DateRateHolder dividends;
			std::string currencyNameAbbr;
	};

	class TradingPosition {
		public:
			TradingPosition() :
			  _units(0) {}

			Size getUnits() {
				return _units;
			}
			void addUnits(Size units) {
				_units += units;
			}
			void clearUnits() {
				_units = 0;
			}

		protected:
			Size _units;
	};

	class AssetPosition : public TradingPosition {
		

		public:
			enum AccrualType { cash, reinvest };

			AssetPosition(boost::shared_ptr<AssetData> a) 
				: _asset(a),
					_pricesIndex(0),
					_dividendsIndex(0),
					_accrualRule(reinvest) { 
			}

			Real getCurrentValue() {
				return _asset->prices.getElementRate(_pricesIndex);
			}

			void setDate(Date d) {
				_pricesIndex = 0;
				_dividendsIndex = 0;
				while ((_asset->prices).getElementDate(_pricesIndex) < d) {
					if (_pricesIndex == ((_asset->prices).size() - 1) || (_asset->prices).getElementDate(_pricesIndex + 1) > d)
						break;
					else
						_pricesIndex ++;
				}
				if ((_asset->dividends).size() != 0) {
					while (_dividendsIndex < ((_asset->dividends).size() - 1) && (_asset->dividends).getElementDate(_dividendsIndex) < d) {
						if (_dividendsIndex == ((_asset->dividends).size() - 1) || (_asset->dividends).getElementDate(_dividendsIndex + 1) > d)
							break;
						else
							_dividendsIndex ++;
					}
				}
			}

			Size update(Date d) {
				Size returnCash = 0;
				QL_ASSERT(d >= (_asset->prices).getElementDate(_pricesIndex),"");
				while ((_asset->prices).getElementDate(_pricesIndex) < d) {
					if (_pricesIndex == ((_asset->prices).size() - 1) || (_asset->prices).getElementDate(_pricesIndex + 1) > d)
						break;
					else
						_pricesIndex ++;
				}
				if ((_asset->dividends).size() != 0 && (_dividendsIndex < (_asset->dividends).size())) {
					while ((_asset->dividends).getElementDate(_dividendsIndex) < d) {
						if (_dividendsIndex == 0) {
							Real divRate = (_asset->dividends).getElementRate(_dividendsIndex) * getUnits();
							if (_accrualRule == AccrualType::cash) 							
								returnCash += static_cast<Size>(divRate);
							else
								addUnits(static_cast<Size>(divRate / getCurrentValue()));
							_dividendsIndex ++;
						}
						if (_dividendsIndex >= ((_asset->dividends).size() - 1) || (_asset->dividends).getElementDate(_dividendsIndex + 1) > d)
							break;
						else {
							Real divRate = (_asset->dividends).getElementRate(_dividendsIndex) * getUnits();
							if (_accrualRule == AccrualType::cash) 							
								returnCash += static_cast<Size>(divRate);
							else
								addUnits(static_cast<Size>(divRate / getCurrentValue()));
							_dividendsIndex ++;
						}

					}
				}
				return returnCash;
			}

			boost::shared_ptr<AssetData> getAsset() {
				return _asset;
			}

			std::vector<Date> getAllAssetDates() {
				std::vector<Date> priceDates = _asset->prices.getAllDates();
				std::vector<Date> divDates = _asset->dividends.getAllDates();
				std::vector<Date> allDates(priceDates.size() + divDates.size());
				std::merge(priceDates.begin(), priceDates.end(), divDates.begin(), divDates.end(), allDates.begin());
				return allDates;
			}

			AccrualType getAccrualRule() {
				return _accrualRule;
			}

			void setAccrualRule(AccrualType a) {
				_accrualRule = a;
			}

			size_t getPricesIndex() {
				return _pricesIndex;
			}

			void setPricesIndex(size_t a) {
				_pricesIndex = a;
			}

			size_t getDividendsIndex() {
				return _dividendsIndex;
			}

			void setDividendsIndex(size_t a) {
				_dividendsIndex = a;
			}




		private:
			boost::shared_ptr<AssetData> _asset;
			size_t _pricesIndex, _dividendsIndex;
			AccrualType _accrualRule;
	};

	class Cash : public TradingPosition {
		public:
			void update(Date d) {
			}

		private:
			std::string _currencyNameAbbr;
	};



	class Portfolio {
		public:
			Portfolio() {
			}

			Portfolio(Portfolio& orig) {
				for (_posItr = orig._positions.begin(); _posItr != orig._positions.end(); ++_posItr) {
					boost::shared_ptr<AssetPosition> ap(new AssetPosition((_posItr->second)->getAsset()));
					ap->addUnits((_posItr->second)->getUnits());
					ap->setAccrualRule((_posItr->second)->getAccrualRule());
					ap->setPricesIndex((_posItr->second)->getPricesIndex());
					ap->setDividendsIndex((_posItr->second)->getDividendsIndex());
					_positions[_posItr->first] = ap;
				}
				_cash = orig._cash;
				_activityDates = orig._activityDates;
			}

			void initialisePortfolio(Date currentDate, Size initialCash) {
				_currentDate = currentDate;
				for (_posItr =_positions.begin(); _posItr != _positions.end(); ++_posItr) {
					_posItr->second->clearUnits();
					_posItr->second->setDate(_currentDate);
				}
				_cash.clearUnits();
				_cash.addUnits(initialCash);
				
			}

			std::vector<Date> getEventDates() {
				return _activityDates;
			}

			void addAsset(std::string name, boost::shared_ptr<AssetData> asset) {
				boost::shared_ptr<AssetPosition> newPosn(new AssetPosition(asset));
				newPosn->setDate(_currentDate);
				_positions[name] = newPosn;
				std::vector<Date> assetDates = newPosn->getAllAssetDates();
				std::vector<Date> newDates(assetDates.size() + _activityDates.size());
				std::merge(assetDates.begin(), assetDates.end(), _activityDates.begin(), _activityDates.end(), newDates.begin());
				std::sort(newDates.begin(),newDates.end());
				std::vector<Date>::iterator it = unique (newDates.begin(), newDates.end());
				newDates.resize(it - newDates.begin());
				_activityDates = newDates;
			}

			void placeOrder(TradeOrder order) {
				Real value = _positions[order.assetName]->getCurrentValue();
				Real buysellFlag = order.buyOrSell == TradeOrder::OrderType::buy ? 1.0 : -1.0;
				_cash.addUnits(- buysellFlag * value * order.notional);
				_positions[order.assetName]->addUnits(buysellFlag * order.notional);
			}

			/*std::vector<std::string> getPositionNames() {
				std::vector<std:string> names = std::vector<std:string>();
				names.push_back("cash");
				for (_posItr =_positions.begin(); _posItr != _positions.end(); ++_posItr)
					names.push_back(_posItr->first);
				return names;
			}*/

			Real getCurrentValue() {
				Real value = _cash.getUnits();
				for (_posItr =_positions.begin(); _posItr != _positions.end(); ++_posItr) {
					boost::shared_ptr<AssetPosition> position = _posItr->second;
					value += position->getCurrentValue() * position->getUnits();
				}
				return value;
			}

			Real getCurrentAssetValue(std::string name) {
				return _positions[name]->getCurrentValue();
			}

			void moveDateForward(Date currentDate) {
				_currentDate = currentDate;
				Size accruedCash = 0;
				for (_posItr =_positions.begin(); _posItr != _positions.end(); ++_posItr)
					accruedCash += _posItr->second->update(_currentDate);
				
				if (accruedCash > 0)
					_cash.addUnits(accruedCash);
			}

			bool isAllCash() {
				for (_posItr =_positions.begin(); _posItr != _positions.end(); ++_posItr) {
					if (_posItr->second->getUnits() > 0)
						return false;
				}
				return true;
			} 
			
			Size getAvailableCash() {
				return _cash.getUnits();
			}


		private:
			Cash _cash;
			std::map<std::string, boost::shared_ptr<AssetPosition> > _positions;
			Date _currentDate;
			std::vector<Date> _activityDates;
			std::map<std::string, boost::shared_ptr<AssetPosition> >::iterator _posItr;

			void update();
	};

	

}



#endif
